package org.walkerljl.commons.lock.impl;

import org.walkerljl.commons.lock.Lock;
import org.walkerljl.commons.log.Logger;
import org.walkerljl.commons.log.LoggerFactory;
import org.walkerljl.commons.redis.RedisClient;
import org.walkerljl.commons.util.NumUtils;

/**
 * 基于Redis实现的分布式锁
 *
 * @author lijunlin
 */
public class RedisLock implements Lock {

    private static final Logger LOGGER = LoggerFactory.getLogger(Lock.class);

    /**
     * Redis Client
     */
    private RedisClient redisClient;

    public RedisLock(RedisClient redisClient) {
        this.redisClient = redisClient;
    }

    @Override
    public boolean lock(String resource, long expectedLockTime) {
        return tryLock(resource, expectedLockTime, Long.MAX_VALUE);
    }

    @Override
    public boolean tryLock(String resource, long expectedLockTime) {
        return tryLock(resource, expectedLockTime, 0);
    }

    @Override
    public boolean tryLock(String resource, long expectedLockTime, long timeout) {
        if (!isEnabled()) {
            return true;
        }
        String messagePrefix = String.format("LOCK[resource = %s, threadId = %s, expectedLockTime = %s, timeout = %s]",
                resource, Thread.currentThread().getId(), expectedLockTime, timeout);
        boolean locked = false;
        long startTime = System.currentTimeMillis();
        do {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(String.format("%s do try lock.", messagePrefix));
            }
            long currentTime = System.currentTimeMillis();
            long timestamp = currentTime + expectedLockTime;

            //try lock
            boolean res = getRedisClient().setNx(resource, timestamp + "");
            if (res) {
                locked = true;
            } else {
                long timestamp1 = NumUtils.parseLong(getRedisClient().get(resource));
                if (currentTime > timestamp1) {
                    long timestamp2 = NumUtils.parseLong(getRedisClient().getSet(resource, (timestamp + "")));
                    if (timestamp1 == timestamp2) {
                        locked = true;
                    }
                }
            }

            //do job
            if (locked) {
                break;
            } else {//sleep
                try {
                    Thread.sleep(getSleepTime());
                } catch (InterruptedException e) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(String.format("%s sleep was interrupted.", messagePrefix));
                    }
                }
            }
        } while (!locked && (System.currentTimeMillis() < (startTime + timeout)));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("%s try lock : %s", messagePrefix, locked));
        }
        return locked;
    }

    @Override
    public void unlock(String resource) {
        if (!isEnabled()) {
            return;
        }
        String messagePrefix = String.format("LOCK[resource = %s, threadId = %s]",
                resource, Thread.currentThread().getId());
        boolean res = false;
        long currentTime = System.currentTimeMillis();
        long timestamp = NumUtils.parseLong(getRedisClient().get(resource));
        if (currentTime < timestamp) {
            res = getRedisClient().del(resource) > 0;
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(String.format("%s unlock resource : %s", messagePrefix, res));
        }
    }

    /**
     * 获取Redis Client
     *
     * @return
     */
    private RedisClient getRedisClient() {
        return redisClient;
    }

    /**
     * 锁是否可用
     *
     * @return
     */
    protected boolean isEnabled() {
        return true;
    }

    /**
     * 睡眠时间
     *
     * @return
     */
    public long getSleepTime() {
        return 2;
    }
}